<?php
declare (strict_types = 1);

namespace daayu\taskload;

use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
use think\facade\Db;

/**
 * 计划任务自动加载
 * 命令格式样例:1 1 * * * python ~/hostloc.py >>~/hostloc.log 2>&1
 */
class TaskLoadCommand extends Command
{
    protected $connection = TaskLoadConfig::DEFAULT_CONNECTION;
    protected $log_path = TaskLoadConfig::LOG_PATH;
    protected $cron_path = TaskLoadConfig::CRON_PATH;
    protected $shell_path = TaskLoadConfig::SHELL_PATH;
    protected $tag_start = TaskLoadConfig::TAG_START;
    protected $tag_end = TaskLoadConfig::TAG_END;

    //service
    private $taskLoadService = null;

    protected function configure()
    {
        $this->tag_start = $this->tag_start . app()->getRootPath() . '###';
        $this->tag_end = $this->tag_end . app()->getRootPath() . '###';
        $this->connection = config('task_load.db_connection', '');
        $this->taskLoadService = new TaskLoadService($this->connection);

        $this->setName('taskLoad')->setDescription('任务自动加载');
        $this->addOption('op', null, Option::VALUE_REQUIRED, '操作', '');
        $this->addOption('key', null, Option::VALUE_REQUIRED, '服务器key', '');
        $this->addOption('data', null, Option::VALUE_REQUIRED, '数据', '');
    }

    protected function execute(Input $input, Output $output)
    {
        $op = $input->getOption('op');
        switch ($op) {
            case 1:
                $key = $input->getOption('key');
                $this->load($key);
                break;
            case 2:
                $this->taskLoadService->createDb();
                echo '创建数据表成功' . PHP_EOL;
                break;
            case 3:
                $data_str = $input->getOption('data');
                if(empty($data_str)){
                    $this->echoOp3();
                }else{
                    parse_str($data_str, $data);
                    $r = $this->taskLoadService->saveProject($data);
                    if($r){
                        echo '保存成功,project_id:' . $r . PHP_EOL;
                    }else{
                        echo '保存失败,error:' . $this->taskLoadService->getError() . PHP_EOL;
                    }
                }
                break;
            case 4:
                $data_str = $input->getOption('data');
                if(empty($data_str)){
                    $this->echoOp4();
                }else{
                    parse_str($data_str, $data);
                    $r = $this->taskLoadService->saveTask($data);
                    if($r){
                        echo '保存成功,task_id:' . $r . PHP_EOL;
                    }else{
                        echo '保存失败,error:' . $this->taskLoadService->getError() . PHP_EOL;
                    }
                }
                break;
            case 5:
                $data_str = $input->getOption('data');
                if(empty($data_str)){
                    $this->echoOp5();
                }else{
                    parse_str($data_str, $data);
                    $r = $this->taskLoadService->saveServer($data);
                    if($r){
                        echo '保存成功,server_id:' . $r . PHP_EOL;
                    }else{
                        echo '保存失败,error:' . $this->taskLoadService->getError() . PHP_EOL;
                    }
                }
                break;
            case 6:
                $data_str = $input->getOption('data');
                if(empty($data_str)){
                    $this->echoOp6();
                }else{
                    parse_str($data_str, $data);
                    $r = $this->taskLoadService->saveServerProject($data);
                    if($r){
                        echo '添加成功,server_id:' . $data['server_id'] . ',project_id:' . $data['project_id'] . PHP_EOL;
                    }else{
                        echo '添加失败,error:' . $this->taskLoadService->getError() . PHP_EOL;
                    }
                }
                break;
            case 7:
                $data_str = $input->getOption('data');
                if(empty($data_str)){
                    $this->echoOp7();
                }else{
                    parse_str($data_str, $data);
                    $r = $this->taskLoadService->deleteServerProject($data);
                    if($r){
                        echo '删除成功,server_id:' . $data['server_id'] . ',project_id:' . $data['project_id'] . PHP_EOL;
                    }else{
                        echo '删除失败,error:' . $this->taskLoadService->getError() . PHP_EOL;
                    }
                }
                break;
            case 8:
                $data_str = $input->getOption('data');
                if(empty($data_str)){
                    $this->echoOp8();
                }else{
                    parse_str($data_str, $data);
                    $r = $this->taskLoadService->addRelate($data);
                    if($r){
                        echo '添加成功,server_id:' . $data['server_id'] . ',task_id:' . $data['task_id'] . PHP_EOL;
                    }else{
                        echo '添加失败,error:' . $this->taskLoadService->getError() . PHP_EOL;
                    }
                }
                break;
            case 9:
                $data_str = $input->getOption('data');
                if(empty($data_str)){
                    $this->echoOp9();
                }else{
                    parse_str($data_str, $data);
                    $r = $this->taskLoadService->deleteRelate($data);
                    if($r){
                        echo '删除成功,server_id:' . $data['server_id'] . ',task_id:' . $data['task_id'] . PHP_EOL;
                    }else{
                        echo '删除失败,error:' . $this->taskLoadService->getError() . PHP_EOL;
                    }
                }
                break;
            case 99:
                break;
            default:
                $this->echoOpDefault();
                break;
        }
    }

    private function load($key)
    {
        echo "[Time]" . date('Y-m-d H:i:s') . PHP_EOL;
        $log_path = $this->log_path;
        $this->initLog($log_path);
        $root_path = app()->getRootPath();
        $root_path_md5 = md5($root_path);
        if(empty($key)){
            echo '服务器key无效，请通过增加key参数，例如: php think taskLoad --op=1 --key=yourkey' . PHP_EOL;
            die;
        }
        $server_info = Db::connect($this->connection)->table(TaskLoadConfig::TABLE_TASK_SERVER)->where('key', $key)->where('status', 1)->find();
        if(!$server_info){
            echo '服务器key无效或状态未启用' . PHP_EOL;
            die;
        }
        $server_id = $server_info['id'];
        $server_projects = Db::connect($this->connection)->table(TaskLoadConfig::TABLE_TASK_SERVER_PROJECT)->where('server_id', $server_id)->column('project_path', 'project_id');
        if(empty($server_projects)){
            echo '服务器未配置项目' . PHP_EOL;
            die;
        }
        $server_project_ids = array_keys($server_projects);
        $task_rs = Db::connect($this->connection)
            ->table(TaskLoadConfig::TABLE_TASK_SERVER_RELATE)->alias('t1')
            ->leftjoin(TaskLoadConfig::TABLE_TASK_PLAN.' t2', 't1.task_id=t2.id')
            ->leftjoin(TaskLoadConfig::TABLE_TASK_PROJECT.' t3', 't1.project_id=t3.id')
            ->where('t1.server_id', '=', $server_id)->where('t1.project_id', 'in', $server_project_ids)
            ->field('t2.id,t2.project_id,t2.name,t2.monitor_num,t2.command_type,t2.command,t2.time_rule,t2.log_file,t2.status,t3.name project_name,t3.status project_project')
            ->order('project_id asc')->select()->toArray();
        if(empty($task_rs)){
            echo '该服务器未配置计划任务' . PHP_EOL;
            $result = file_put_contents($this->cron_path, implode(PHP_EOL, $this->getCrons()));
            die;
        }
        $task_line = [];
        foreach ($task_rs as $k => $v) {
            $task_line[] = "#[{$v['project_name']}:{$v['project_id']}]-TaskId:{$v['id']}] {$v['name']}";
            if($v['command_type'] == 1){
                //think命令
                if(!TaskLoadForm::checkCommand($v['command'])){
                    echo '命令格式不正确' . $v['id'] . PHP_EOL;
                    die;
                }
                $cron_line = $server_info['php_path'] . " " . $server_projects[$v['project_id']] . "think {$v['command']} >> " . $log_path . $v['log_file'] . "_{$root_path_md5}_`date +\%Y\%m`.log";
            }elseif($v['command_type'] == 2){
                //curl命令
                if(!TaskLoadForm::checkUrl($v['command'])){
                    echo 'url格式不正确' . $v['id'] . PHP_EOL;
                    die;
                }
                $cron_line = $server_info['curl_path'] . " {$v['command']} >> " . $log_path . $v['log_file'] . "_{$root_path_md5}_`date +\%Y\%m`.log";
            }elseif($v['command_type'] == 3){
                //bash命令
                if(!TaskLoadForm::checkCommand($v['command'])){
                    echo '命令格式不正确' . $v['id'] . PHP_EOL;
                    die;
                }
                $cron_line = $server_info['bash_path'] . " " . $server_projects[$v['project_id']] . "{$v['command']} >> " . $log_path . $v['log_file'] . "_{$root_path_md5}_`date +\%Y\%m`.log";
            }elseif($v['command_type'] == 4){
                //python命令
                if(!TaskLoadForm::checkCommand($v['command'])){
                    echo '命令格式不正确' . $v['id'] . PHP_EOL;
                    die;
                }
                $cron_line = $server_info['python_path'] . " " . $server_projects[$v['project_id']] . "{$v['command']} >> " . $log_path . $v['log_file'] . "_{$root_path_md5}_`date +\%Y\%m`.log";
            }elseif($v['command_type'] == 5){
                //php命令
                if(!TaskLoadForm::checkCommand($v['command'])){
                    echo '命令格式不正确' . $v['id'] . PHP_EOL;
                    die;
                }
                $cron_line = $server_info['php_path'] . " " . $server_projects[$v['project_id']] . "{$v['command']} >> " . $log_path . $v['log_file'] . "_{$root_path_md5}_`date +\%Y\%m`.log";
            }
            if($v['monitor_num'] > 0){
                $cron_line = str_replace('"', "'", $cron_line);
                $cron_line = "{$v['time_rule']} " . 'bash '.$root_path . $this->shell_path .'TaskMonitor.sh "'.$cron_line . '" ' . $v['monitor_num'];
            }else{
                $cron_line = "{$v['time_rule']} " . $cron_line . " 2>&1 &";
            }
            if(!$v['status'] || !$v['project_status']){
                $cron_line = '#' . $cron_line;
            }
            $task_line[] = $cron_line;
        }
        $crons = $this->getCrons($this->cron_path);
        $crons[] = $this->tag_start;
        $crons = array_merge($crons, $task_line);
        $crons[] = $this->tag_end;
        // echo implode(PHP_EOL, $crons);die;
        $result = file_put_contents($this->cron_path, implode(PHP_EOL, $crons));
        if($result){
            echo "计划任务更新成功" . PHP_EOL;
        }else{
            echo "计划任务写入文件失败" . PHP_EOL;
        }
    }

    private function initLog($log_path)
    {
        //如果目录不存在，创建目录
        if (!is_dir($log_path)){
            //第三个参数是“true”表示能创建多级目录，iconv防止中文目录乱码
            $res=mkdir(iconv("UTF-8", "GBK", $log_path),0777,true); 
            if ($res){
                echo "日志目录$log_path 创建成功" . PHP_EOL;
            }else{
                echo "日志目录$log_path 创建失败" . PHP_EOL;
                die;
            }
        }
    }

    /**
     * 获取当前计划任务
     */
    private function getCrons()
    {
        if(!file_exists($this->cron_path)) return [];
        $crons = file_get_contents($this->cron_path);
        $crons = explode(PHP_EOL, $crons);
        $need_delete = 0;
        foreach ($crons as $k => $v) {
            if($v == $this->tag_start){
                $need_delete = 1;
            }else if($v == $this->tag_end){
                unset($crons[$k]);
                $need_delete = 0;
                break;
            }
            if($need_delete){
                unset($crons[$k]);
            }
        }
        return $crons;
    }

    /**
     * 操作系统类型
     * @return int 1 linux 2 其他
     */
    public static function osType()
    {
        $os_name=PHP_OS;
        if(strpos($os_name,"Linux")!==false){
            return 1;
        }else if(strpos($os_name,"WIN")!==false){
            return 2;
        }
        return false;
    }

    private function echoOpDefault(){
        echo '脚本支持的操作有以下几种：' . PHP_EOL;
        echo "1.远程加载计划任务：" . PHP_EOL;
        echo "  php think taskLoad --op=1 --key=\"服务器key\"" . PHP_EOL;
        echo "2.自动创建数据库：" . PHP_EOL;
        echo "  php think taskLoad --op=2" . PHP_EOL;
        echo "3.添加或修改项目：" . PHP_EOL;
        echo "  php think taskLoad --op=3 --data=\"query字符串，字段值需要urlencode\"" . PHP_EOL;
        echo "4.添加或修改任务：" . PHP_EOL;
        echo "  php think taskLoad --op=4 --data=\"query字符串，字段值需要urlencode\"" . PHP_EOL;
        echo "5.添加或修改服务器：" . PHP_EOL;
        echo "  php think taskLoad --op=5 --data=\"query字符串，字段值需要urlencode\"" . PHP_EOL;
        echo "6.服务器添加项目：" . PHP_EOL;
        echo "  php think taskLoad --op=6 --data=\"server_id=0&project_id=0&project_path=\"" . PHP_EOL;
        echo "7.服务器删除项目：" . PHP_EOL;
        echo "  php think taskLoad --op=7 --data=\"server_id=0&project_id=0&project_path=\"" . PHP_EOL;
        echo "8.服务器添加任务：" . PHP_EOL;
        echo "  php think taskLoad --op=8 --data=\"server_id=0&task_id=0\"" . PHP_EOL;
        echo "9.服务器删除任务：" . PHP_EOL;
        echo "  php think taskLoad --op=9 --data=\"server_id=0&task_id=0\"" . PHP_EOL;
    }

    private function echoOp3(){
        echo 'data格式说明：' . PHP_EOL;
        echo '--data="id=id&name=项目名称&describe=描述&status=状态值"' . PHP_EOL;
        echo '每个字段都需要进行urlencode转换' . PHP_EOL;
        echo 'id：           项目id, 该参数存在时修改相应的记录;' . PHP_EOL;
        echo 'name:          项目名称;' . PHP_EOL;
        echo 'describe:      项目描述;' . PHP_EOL;
        echo 'status：       状态值 0禁用 1启用;' . PHP_EOL;
    }

    private function echoOp4(){
        echo 'data格式说明：' . PHP_EOL;
        echo '--data="id=id&project_id=项目id&name=任务名称&describe=任务描述&monitor_num=监控数量&command_type=命令类型&command=命令文本&time_rule=时间规则&log_file=日志文件名&status=状态值"' . PHP_EOL;
        echo '每个字段都需要进行urlencode转换' . PHP_EOL;
        echo 'id：           任务id, 该参数存在时修改相应的记录;' . PHP_EOL;
        echo 'project_id：   项目id;' . PHP_EOL;
        echo 'name:          任务名称;' . PHP_EOL;
        echo 'describe:      任务描述;' . PHP_EOL;
        echo 'monitor_num：  监控数量说明 0普通任务，大于0监控任务， 监控任务时保持monitor_num个进程，数量不足时会自动创建进程;' . PHP_EOL;
        echo 'command_type： 命令类型 1think任务 2url任务' . PHP_EOL;
        echo 'command：      命令文本 command_type=1时，输入"think"之后的命令，command_type=2时，输入url地址，url需要urlencode;' . PHP_EOL;
        echo 'time_rule：    执行频率规则，与crontab规则一致，格式:[分]|[时]|[日]|[月]|[周]，例如:每分钟执行(规则：*/1|*|*|*|*)每日2点15分执行(规则：15|02|*/1|*|*);' . PHP_EOL;
        echo 'log_file：     日志文件名，不带后缀;' . PHP_EOL;
        echo 'status：       状态值 0禁用 1启用;' . PHP_EOL;
    }

    private function echoOp5(){
        echo 'data格式说明：' . PHP_EOL;
        echo '--data="id=id&name=项目名称&describe=描述&status=状态值"' . PHP_EOL;
        echo '每个字段都需要进行urlencode转换' . PHP_EOL;
        echo 'id：           服务器id, 该参数存在时修改相应的记录;' . PHP_EOL;
        echo 'name:          服务器名称;' . PHP_EOL;
        echo 'describe:      服务器描述;' . PHP_EOL;
        echo 'status：       状态值 0禁用 1启用;' . PHP_EOL;
    }

    private function echoOp6(){
        echo 'data格式说明：' . PHP_EOL;
        echo '--data="server_id=0&project_id=0&project_path="' . PHP_EOL;
        echo 'server_id：     服务器id;' . PHP_EOL;
        echo 'project_id：    项目id;' . PHP_EOL;
        echo 'project_path：  项目路径;' . PHP_EOL;
    }

    private function echoOp7(){
        echo 'data格式说明：' . PHP_EOL;
        echo '--data="server_id=0&project_id=0&project_path="' . PHP_EOL;
        echo 'server_id：     服务器id;' . PHP_EOL;
        echo 'project_id：    项目id;' . PHP_EOL;
        echo 'project_path：  项目路径;' . PHP_EOL;
    }

    private function echoOp8(){
        echo 'data格式说明：' . PHP_EOL;
        echo '--data="server_id=0&task_id=0"' . PHP_EOL;
        echo 'server_id：     服务器id;' . PHP_EOL;
        echo 'task_id：       任务id;' . PHP_EOL;
    }

    private function echoOp9(){
        echo 'data格式说明：' . PHP_EOL;
        echo '--data="server_id=0&task_id=0"' . PHP_EOL;
        echo 'server_id：     服务器id;' . PHP_EOL;
        echo 'task_id：       任务id;' . PHP_EOL;
    }
}
